﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using ca2simulator.shared;
using ca2simulator.server.simulator;
using ca2simulator.client.model.transfer;
using ca2simulator.shared.wcf;

namespace ca2simulator.server.contract
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    public class CA2Service : ICA2SimulationService, ISimulatorListener
    {
        

        List<ISimulationCallback> subscribers = new List<ISimulationCallback>();
        LifeSimulator _Simulator;

        public CA2Service(CAContract ca)
        {
            // Setup the simulator
            _Simulator = new LifeSimulator();
            _Simulator.Simulator.AddListener(this);

           
        }

        #region CA2 Serive methods
        public LatticeContract GetLattice()
        {
            return new LatticeContract(_Simulator.Simulator.GetLattice());
        }
        public void Run()
        {
            _Simulator.Run();
        }
        public void Halt()
        {
            _Simulator.Stop();
        }
        public LatticeContract Step(int numStep)
        {
            for (int i = 0; i < numStep; i++)
            {
                _Simulator.Step();
            }
            return new LatticeContract(_Simulator.Simulator.GetLattice());
        }

        #region Subscribe/unsubscribe and announce methods
        // From: http://idunno.org/archive/2008/05/29/wcf-callbacks-a-beginners-guide.aspx

        public bool Subscribe()
        { 
            try
            {
                ISimulationCallback callback = OperationContext.Current.GetCallbackChannel<ISimulationCallback>();
                if (!subscribers.Contains(callback))
                    subscribers.Add(callback);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool Unsubscribe()
        {
            try
            {
                ISimulationCallback callback = OperationContext.Current.GetCallbackChannel<ISimulationCallback>();
                if (subscribers.Contains(callback))
                    subscribers.Remove(callback);
                return true;
            }
            catch
            {
                return false;
            }
        }

        private void AccounceUpdate(Lattice lattice)
        {
            subscribers.ForEach(delegate(ISimulationCallback callback)
            {
                LatticeContract lat = new LatticeContract(lattice);
                if (((ICommunicationObject)callback).State == CommunicationState.Opened)
                {
                    callback.OnLatticeUpdate(lat);
                }
                else
                {
                    subscribers.Remove(callback);
                }
            });
        }

        #endregion
        #endregion

        #region Simulation Listener methods
        public void OnLatticeUpdate()
        {
            this.AccounceUpdate(_Simulator.Simulator.GetLattice());
        }

        public void OnSimulationFinish()
        {
            throw new NotImplementedException();
        }
        #endregion
    }

    public class LifeSimulator
    {
        private ISimulator _Sim;
        public ISimulator Simulator { get { return _Sim; } }

        public LifeSimulator()
        {
            CellState stateDead = new CellState();
            stateDead.State = 0;
            CellState stateLive = new CellState();
            stateLive.State = 1;

            Lattice lat = new Lattice(stateDead, 800, 800);

            Lattice iniLat = new Lattice(stateDead, 800, 800);
            for (int i = 0; i < 20; i++)
            {
                addPattern(iniLat, getPatternByName("Glider"), 1 + 5 * i, 1, stateLive);
            }
            //TODO Transfer function needs uploaded from client
            CA2 ca = new CA2(lat, new BasicLifeTransfer(), iniLat);
            //CA2 ca = new CA2(lat, new StaticTransfer(), iniLat);
            _Sim = new BasicSimulator(ca);
        }

        public void Step()
        {
            _Sim.Step();
        }

        public void Run()
        {
            _Sim.Run();
        }

        public void Stop()
        {
            _Sim.Halt();
        }

        private static void addPattern(Lattice lat, List<CellLocation> pattern, int r, int c, CellState stateToSet)
        {
            Cell[,] cells = lat.Cells;
            foreach (CellLocation cs in pattern)
            {
                cells[r + cs.row, c + cs.col].CellState = stateToSet;
            }
        }

        private static List<CellLocation> getPatternByName(string pattern)
        {
            List<CellLocation> cells = new List<CellLocation>();
            if ("Glider".Equals(pattern, StringComparison.OrdinalIgnoreCase))
            {
                cells.Add(new CellLocation(0, 1));
                cells.Add(new CellLocation(1, 2));
                cells.Add(new CellLocation(2, 0));
                cells.Add(new CellLocation(2, 1));
                cells.Add(new CellLocation(2, 2));
            }
            else if ("Block".Equals(pattern, StringComparison.OrdinalIgnoreCase))
            {
                cells.Add(new CellLocation(0, 0));
                cells.Add(new CellLocation(0, 1));
                cells.Add(new CellLocation(1, 0));
                cells.Add(new CellLocation(1, 1));
            }

            return cells;

        }
    }

    public class Program
    {
        const string ADDR = "http://localhost:8000/CA2Service";

        public static void Main(string[] args)
        {
            //Create the service: http://msdn.microsoft.com/en-us/library/ms730935.aspx
            Uri baseAddress = new Uri(ADDR);
            ServiceHost selfHost = new ServiceHost(typeof(CA2Service), baseAddress);
            try
            {
                // Add endpoing
                selfHost.AddServiceEndpoint(
                    typeof(CA2Service),
                    new NetTcpBinding(),
                    "CA2Service");

                //Open the service
                selfHost.Open();
                Console.WriteLine("The service is ready.");
                Console.WriteLine("Press <ENTER> to terminate service.");
                Console.WriteLine();
                Console.ReadLine();

                // Close the ServiceHostBase to shutdown the service.
                selfHost.Close();
            }
            catch (CommunicationException ce)
            {
                Console.WriteLine("An exception occured: {0}", ce.Message);
                selfHost.Abort();
            }
        }
    }
    
}
